import json
import logging

import PIL
import cv2
import numpy as np
from PIL.Image import Image
from pydantic import BaseModel, Field


class MorphOpenParams(BaseModel):
    shape: tuple = Field(default=(8, 8))
    morph_op: int = Field(default=cv2.MORPH_OPEN)


class MedianBlurParams(BaseModel):
    ksize: int = Field(default=8)
    morph_op: int = Field(default=cv2.MORPH_OPEN)


class FindContoursParams(BaseModel):
    mode: int = Field(default=cv2.RETR_TREE)
    method: int = Field(default=cv2.CHAIN_APPROX_SIMPLE)


class DrawContoursParams(BaseModel):
    contour_idx: int = Field(default=-1)
    color: list = Field(default=[0, 255, 0])
    thickness: int = Field(default=1)


class DrawContoursParams(BaseModel):
    contour_idx: int = Field(default=-1)
    color: list = Field(default=[0, 255, 0])
    thickness: int = Field(default=1)


class LabelParams(BaseModel):
    font_type: int = Field(default=cv2.FONT_HERSHEY_PLAIN)
    font_scale: float = Field(default=1.5)
    color: list = Field(default=[0, 255, 255])
    thickness: int = Field(default=2)


class FindAndDrawContoursParams(BaseModel):
    find_params: FindContoursParams
    area_thresh: int = Field(default=100)
    draw_params: DrawContoursParams
    label_params: LabelParams


class GaussBlurParams(BaseModel):
    ksize: tuple = Field(default=(5, 5))
    sigma_x: int = Field(default=1)


class CannyBlurParams(BaseModel):
    threshold1: int = Field(default=127)
    threshold2: int = Field(default=255)
    aperture_size: int = Field(default=0)


class SobelBlurParams(BaseModel):
    alpha: float = Field(default=0.5)
    beta: float = Field(default=0.5)
    gamma: float = Field(default=0)


class ErodeParams(BaseModel):
    shape: list = Field(default=[3, 3])
    iterations: int = Field(default=3)


class OpencvAlgoWrapper:
    def __init__(self, logger: logging.Logger):
        self.logger = logger
        default_json_file_path = 'default_algo_params.json'
        with open(default_json_file_path, 'r', encoding='utf-8') as file:
            self.default_algo_item_list = json.load(file)
        self.select_algo_options = {}
        for algo_item in self.default_algo_item_list:
            self.select_algo_options[algo_item['algo_alias']] = algo_item['algo_name']

    def extract_dict_by_fields(self, original_dict, fields_to_extract):
        return {key: original_dict[key] for key in fields_to_extract if key in original_dict}

    def get_select_algo_list(self):
        return self.select_algo_options

    def get_default_algo_item_by_alias(self, algo_alias: str):
        for algo in self.default_algo_item_list:
            if algo['algo_alias'] == algo_alias:
                return algo
        return {'algo_alias': 'None', 'algo_name': 'Not supported'}

    def _to_pil_image(self, img_src: cv2.typing.MatLike):
        # 将BGR转换为RGB，因为OpenCV默认使用BGR格式
        img_result = cv2.cvtColor(img_src, cv2.COLOR_BGR2RGB)

        return PIL.Image.fromarray(img_result)

    def opencv_openning(self, img_src: cv2.typing.MatLike, shape):
        k = np.ones(shape, np.uint8)
        img_result = cv2.morphologyEx(img_src, cv2.MORPH_OPEN, k)

        return img_result, self._to_pil_image(img_result)

    def opencv_erode(self, img_src: cv2.typing.MatLike, params: ErodeParams):
        k = np.ones(tuple(params.shape), np.uint8)
        img_result = cv2.erode(img_src, k, iterations=params.iterations)

        return img_result, self._to_pil_image(img_result)

    def opencv_grey(self, img_src: cv2.typing.MatLike):
        img_result = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY)
        return img_result, self._to_pil_image(img_result)

    def opencv_threshold(self, img_src: cv2.typing.MatLike, thresh: float, maxval: float):
        _, img_result = cv2.threshold(img_src, thresh, maxval, cv2.THRESH_OTSU)
        return img_result, self._to_pil_image(img_result)

    def opencv_find_and_draw_contours(self, img_src: cv2.typing.MatLike, params: FindAndDrawContoursParams):
        contours, hierarchy = cv2.findContours(img_src,
                                               params.find_params.mode,
                                               params.find_params.method)
        contours_ok = []
        for i in contours:
            if cv2.contourArea(i) > params.area_thresh:
                contours_ok.append(i)

        img_result = cv2.drawContours(img_src,
                                      contours_ok,
                                      params.draw_params.contour_idx,
                                      tuple(params.draw_params.color),
                                      params.draw_params.thickness)

        for i, j in zip(contours_ok, range(len(contours_ok))):
            M = cv2.moments(i)
            cX = int(M["m10"] / M["m00"])
            cY = int(M["m01"] / M["m00"])
            cv2.putText(img_result, str(j),
                        (cX, cY),
                        params.label_params.font_type,
                        params.label_params.font_scale,
                        tuple(params.label_params.color),
                        params.label_params.thickness)

        return img_result, self._to_pil_image(img_result)

    def opencv_guass_blur(self, img_src: cv2.typing.MatLike, params: GaussBlurParams):
        img_result = cv2.GaussianBlur(img_src, tuple(params.ksize), params.sigma_x)
        return img_result, self._to_pil_image(img_result)

    def opencv_find_exhull(self, img_src: cv2.typing.MatLike):
        img_result = cv2.GaussianBlur(img_src, (5, 5), 1)
        return img_result, self._to_pil_image(img_result)

    def opencv_median_blur(self, img_src: cv2.typing.MatLike, params: MedianBlurParams):
        img_result = cv2.medianBlur(img_src, params.ksize)
        return img_result, self._to_pil_image(img_result)

    def opencv_canny_blur_for_gray(self, img_src: cv2.typing.MatLike, params: CannyBlurParams):
        img_result = cv2.Canny(img_src, params.threshold1, params.threshold2, params.aperture_size)
        return img_result, self._to_pil_image(img_result)

    def opencv_canny_blur_for_bgr(self, img_src: cv2.typing.MatLike, params: CannyBlurParams):
        img_src_gray = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY)
        return self.opencv_canny_blur_for_gray(img_src_gray, params)

    def opencv_sobel_blur(self, img_src: cv2.typing.MatLike, params: SobelBlurParams):
        img_src_x = cv2.Sobel(img_src, cv2.CV_16S, 1, 0)
        img_src_y = cv2.Sobel(img_src, cv2.CV_16S, 0, 1)

        # img_src_x = cv2.Sobel(img_src, cv2.CV_16S, 1, 0, ksize=-1)
        # img_src_y = cv2.Sobel(img_src, cv2.CV_16S, 0, 1, ksize=-1)

        img_src_absx = cv2.convertScaleAbs(img_src_x)
        img_src_absy = cv2.convertScaleAbs(img_src_y)

        img_result = cv2.addWeighted(img_src_absx, params.alpha, img_src_absy, params.beta, params.gamma)
        return img_result, self._to_pil_image(img_result)
